#pragma once
#include <iostream>
#include <vector>
#include "Sem.hpp"
#include "Mutex.hpp"
using namespace std;
using namespace SemModule;   // 信号量
using namespace MutexModule; // 互斥锁
static const int gcap = 5;   // 默认容量

template <typename T>
class RingQueue
{
public:
    RingQueue(int cap = gcap)
        : _rq(cap), _cap(cap), blank_sem(cap), _p_step(0), data_sem(0), _c_step(0)
    {
    }
    void Eququeue(const T &in)
    {
        // 线程先将资源量给瓜分了
        // 然后再进行排队的操作，先申请信号量，然后再申请锁，这样的效率比较高
        // 生产者

        // 1.申请信号量，申请空位置信号量
        _blank_sem.P(); // 申请信号量
        {
            LockGuard _lock(_pmutex); // 自动初始化和销毁锁
            //_pmutex.Lock(); // 申请锁
            // 2.生产数据
            _rq[_p_step] = in; // 将in 这个数据放到数组位置里面
            // 3.更新下标
            ++_p_step;
            // 4.因为是环形队列，所以如果超过了容量，就回到0。所以我们得进行取模维持环形特性
            _p_step %= _cap;

            //_pmutex.Unlock();
        }
        _data_sem.V(); // 通知消费者，有数据了
    }
    void Pop(T *out)
    {
        // 消费者

        // 1.申请信号量，申请数据位置信号量
        _data_sem.P(); // 一开始是数据信号量是0的，所以P操作是会阻塞住的，直到生产者通知消费者有数据了
        {              //_cmutex.Lock();
            LockGuard _lock(_cmutex); // 自动初始化和销毁锁
            // 2.消费数据
            *out = _rq[_c_step]; // 取出数据
            // 3.更新下标
            ++_c_step;
            // 4.因为是环形队列，所以如果超过了容量，就回到0。所以我们得进行取模维持环形特性
            _c_step %= _cap;
        }
        _blank_sem.V(); // 通知生产者，空位置可以用了。数据被拿走了，那么这个位置就空出来了
        //_cmutex.Unlock();
    }

    ~RingQueue()
    {
    }

private:
    vector<T> _rq;
    int _cap;

    // 生产者
    Sem _blank_sem; // 关注的是空的资源
    int _p_step;    // 下标
    // 消费者
    Sem _data_sem; // 关注的是数据位置
    int _c_step;

    // 两个下标只有为空或者为满的时候才会出现相等的情况

    // 维护多生产多消费，我们需要两把锁，一个用来维持生产者之间的互斥关系，一个用来维持消费者之间的互斥关系。
    // 生产者和消费者之间是互斥的，但是生产者和生产者之间是不互斥的。所以我们需要两个信号量，一个用来维持生产者之间的互斥关系，一个用来维持消费者之间的互斥关系。
    Mutex _cmutex;
    Mutex _pmutex;
};
